import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import javax.imageio.ImageIO;
import java.io.File;

/*
 * This class was adapted from code found online, which provided great assistance in figuring out mjpeg-streamer on the yun.
 * MJPEG streaming is not very well documented online so this has been quite useful! The byline is attached with a link:
 *
 * Given an extended JPanel and URL read and create BufferedImages to be displayed from a MJPEG stream
 * @author shrub34 Copyright 2012
 * Free for reuse, just please give me a credit if it is for a redistributed package
 * 
 * The code we adapted from can be found at the following locations as of March 3rd 2014:
 * 
 * https://github.com/karlqui/RobotProject/blob/master/mjpeg/MjpegRunner.java
 * http://thistleshrub.net/Joomla/index.php?option=com_content&view=article&id=115:displaying-streamed-mjpeg-in-java&catid=43:robotics&Itemid=64
 *
 */

/* TODO ClassLevelCommentBlock
 * 
 * @author Jake Parsons MUN#201030616. Assisting code and respective developers referenced in this class.
 */

public class MjpegStreamer implements Runnable {
	//timing-related members
	private static final int imgPullRate = 1000 / 60;
	
	//MIME messaging members
	private static final String CONTENT_LEN = "Content-Length: ";
	private static final String CONTENT_TYP = "Content-Type: image/jpeg";
	
	//Network-related members
	private URL streamUrl;
	private URLConnection urlToStream;
	private String httpUsername;
	private String httpPassword;
	
	private InputStream urlMjpegStream;
	private BufferedReader urlMjpegBufRead; //DEPRECATED TODO REMOVE!
	private StringWriter mjpegWriter;
	
	private ConnectedDeviceBuffer vidOutBuf;
	
	//Stream-related members
	private static final int READ_TIMEOUT = 1500;
	
	//Thread-related members
	private boolean stopRequested = false;
	
	public MjpegStreamer(ConnectedDeviceBuffer outBuf, URL mjpegSrcUrl) throws IOException {
		this(outBuf, mjpegSrcUrl, null, null);
	}
	
	public MjpegStreamer(ConnectedDeviceBuffer outBuf, URL mjpegSrcUrl, String userIn, String passIn) throws IOException { 
		//set up authentication (if it was specified!)
		if(this.httpUsername != null && this.httpPassword != null) {
			Authenticator.setDefault(new HttpAuthenticator(this.httpUsername, this.httpPassword));
		}
		//set the buffer
		this.vidOutBuf = outBuf;
		
		//try to connect to the mjpeg stream
		this.streamUrl = mjpegSrcUrl;
		//this.urlToStream = mjpegSrcUrl.openConnection();
		//this.urlToStream.setReadTimeout(MjpegStreamer.READ_TIMEOUT);
		//this.urlMjpegStream = this.urlToStream.getInputStream();
		this.mjpegWriter = new StringWriter(/*128*/);
	}

	public void run() {
		System.out.println("Video thread starts...");
		try {
			//proper method TODO fix me
			this.urlToStream = this.streamUrl.openConnection();
			this.urlToStream.setReadTimeout(MjpegStreamer.READ_TIMEOUT);
			this.urlMjpegStream = this.urlToStream.getInputStream();

			//the old method follows
			//this.urlMjpegBufRead = new BufferedReader(new InputStreamReader(this.streamUrl.openStream()));
		}catch(IOException ioErr) { ioErr.printStackTrace(); }
		
		int saveIter = 0;		
		while(!this.stopRequested) {
			try {
				byte[] image = getNextImg(); //TODO please make that method work again
				//int[] imageAsInts = imageToIntArr(image); //TODO see line above
				//byte[] image = altGetNextImg();
				//if(image == null) { System.out.println("null image"); continue; }
				//this.vidOutBuf.produce(imageAsInts);
				if(saveIter == 0) {
					//System.out.println("Wrote image to file");
					ByteArrayInputStream imgByteArrInStrm = new ByteArrayInputStream(image);
					BufferedImage jpegImage = ImageIO.read(imgByteArrInStrm);
					if(jpegImage != null) {
						System.out.println("Saving an image");
						File outputImg = new File("savedFrame.jpeg");
						ImageIO.write(jpegImage, "jpeg", outputImg);
					}
					else{
						System.out.println("We got a null image");
						continue;
					}
				}
			}catch(SocketTimeoutException stErr) {
				stErr.printStackTrace();
			}catch(IOException ioErr) {
				ioErr.printStackTrace();
			}
			saveIter = (saveIter + 1) % 30;
		}
		this.closeStreams();
	}
	
	public synchronized void requestStop() {
		this.stopRequested = true;
	}
	
	private byte[] getNextImg() throws IOException {
		boolean msgContainsHeader = false;
		int currentByte = -1;
		//System.out.println("start getNextImg");
		
		String header = null;
		while((currentByte = this.urlMjpegStream.read()) > -1 && !msgContainsHeader) {
			//do something with the current byte if you want
			this.mjpegWriter.write(currentByte);
			
			String msgInProgress = this.mjpegWriter.toString();
			int indexOfLenInMsg = msgInProgress.indexOf(MjpegStreamer.CONTENT_LEN);
			int indexOfTypInMsg = msgInProgress.indexOf(MjpegStreamer.CONTENT_TYP);
			if(indexOfLenInMsg > 0 && indexOfTypInMsg > 0 && currentByte == 255) {
				msgContainsHeader = true;
				header = msgInProgress;
				//System.out.println(header);
			}
		}
		//while((this.urlMjpegStream.read() != 255)) { } //skip the rest of the buffer
		//System.out.println("end of buffer");
		int msgLength = getMimeContentLen(header);
		byte[] completeImage = new byte[msgLength + 1];//that added entry is for the line below
		completeImage[0] = (byte) 44;//255; //above we read until we hit the 255 signifying the end of the header. we need it back!
		int offset = 1;
		int bytesRead = 0;
		/*
		while(offset < completeImage.length &&
				(bytesRead = this.urlMjpegStream.read(completeImage, offset, completeImage.length-offset)) >= 0) {
			offset += bytesRead;
		}*/
		
		/*bytesRead = this.urlMjpegStream.read(completeImage, offset, completeImage.length-offset);
		completeImage[completeImage.length - 1] = -1;
		System.out.println("Image received, read " + (1 + bytesRead) + " of " + msgLength + " B : last B was " + completeImage[bytesRead - 1]);*/
		
		for(int i = 1; i < completeImage.length; i++) {
			completeImage[i] = (byte) this.urlMjpegStream.read();
			if(i % 10 == 0) {
				System.out.println();
			}
			System.out.print(completeImage[i] + " ");
		}
		
		this.mjpegWriter = new StringWriter(/*128*/);
		
		return completeImage;
	}
	
	private byte[] altGetNextImg() throws IOException {//TODO remove this!
		int imgCount = 0;
		try {
			String inputLine;
			int lineCount = 0;
			boolean lineCountStart = false;
			boolean lineCountEnd = false;
			boolean saveImage = false;
			while( ((inputLine = this.urlMjpegBufRead.readLine()) != null) ) {
				//"should be checking just for '--' probably" ? ? ?
				//if(inputLine.lastIndexOf("--boundarydonotcross") > -1) {
				if(inputLine.lastIndexOf("--myboundary") > -1) {
					//System.out.println("found boundary");
            		saveImage = false;
            		lineCountEnd = lineCountStart;
            		lineCountStart = true;
            		
            		//System.out.println("Got a new boundary");
            		//System.out.println(inputLine);
				}
            	else if (lineCountStart) {
            		lineCount++;
            		if (lineCount >= 2) {
            			lineCount = 0;
            			lineCountStart = false;
            			imgCount++;
            			saveImage = true;
                		//System.out.println("Starting a new image");
            		}
            	}
            	else if (saveImage) {
            		//System.out.println("Saving an image");
            		char[] imageOut = inputLine.toCharArray();
            		int[] intImgOut = new int[imageOut.length];
            		for(int i = 0; i < intImgOut.length; i++) {
            			intImgOut[i] = (int) imageOut[i];
            		}
            		//return intImgOut;
            		return inputLine.getBytes();
            	}
            	else {
            		//System.out.println("What's this:");
            		//System.out.println(inputLine);
            		return null;
            	}
            }
			return null;
		}catch(IOException ioErr){
			ioErr.printStackTrace();
			return null;
		}
		//put the image together now
	}
	
	private int[] imageToIntArr(byte[] imgToConvert) {
		//we'll be needing this since we may want to save images from the server
		//but to go to the stream we need integers
		int[] outArr = new int[imgToConvert.length];
		for(int i = 0; i < imgToConvert.length; i++) {
			outArr[i] = (int) imgToConvert[i];
		}
		return outArr;
	}
	
	private static int getMimeContentLen(String mimeHeader) {
		int contentLenIndx = mimeHeader.indexOf(MjpegStreamer.CONTENT_LEN);
		int imageOffsetInMimeMsg = contentLenIndx + MjpegStreamer.CONTENT_LEN.length();
		int msgTermIndx = mimeHeader.indexOf('\n', contentLenIndx);
		
		String length = mimeHeader.substring(imageOffsetInMimeMsg, msgTermIndx).trim();
		int mimeContentLength = Integer.parseInt(length);
		return mimeContentLength;
	}
	
	private boolean closeStreams() {
		try {
			this.urlMjpegStream.close();
			this.urlMjpegBufRead.close(); //DEPRECATED TODO REMOVE
			return true;
		}catch(IOException ioErr){
			ioErr.printStackTrace();
			return false;
		}
	}

	static class HttpAuthenticator extends Authenticator {
		private String un;
		private String pass;
		
		public HttpAuthenticator(String userIn, String passIn) {
			this.un = userIn;
			this.pass = passIn;
		}
		
		protected PasswordAuthentication getPwAuth() {
			return new PasswordAuthentication(this.un, this.pass.toCharArray());
		}
	}
}
